package session

import (
	"context"
	"crypto/md5"
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"time"

	"github.com/gorilla-go/container"
	"github.com/gorilla-go/pig/constant"
)

var envCache = make(map[string]string)

type Session struct {
	values     map[string]any
	lastReadAt time.Time
}

var sessionMap = make(map[string]*Session)

func init() {
	// 清理过期的 session
	go func() {
		sessionCleanInterval := getEnv("SESSION_CLEAN_INTERVAL", "10m")
		interval, err := time.ParseDuration(sessionCleanInterval)
		if err != nil {
			interval = time.Minute * 10
		}
		ticker := time.NewTicker(interval)
		for range ticker.C {
			for sid, item := range sessionMap {
				if time.Since(item.lastReadAt) > getSessionExpire() {
					delete(sessionMap, sid)
				}
			}
		}
	}()
}

// 获取 session
func GetSession(ctx context.Context) *Session {
	return container.GetMustSingletonByAlias[*Session](
		ctx.Value(constant.Container).(*container.Container),
		constant.Session,
	)
}

// 判断 session 是否设置
func IsSet(ctx context.Context, key string) bool {
	_, ok := GetSession(ctx).values[key]
	return ok
}

// 获取 session 值
func GetValue[T any](ctx context.Context, key string) (T, bool) {
	if !IsSet(ctx, key) {
		var zero T
		return zero, false
	}
	return GetSession(ctx).values[key].(T), true
}

// 获取 session 值，如果值不存在则 panic
func GetMustValue[T any](ctx context.Context, key string) T {
	value, ok := GetValue[T](ctx, key)
	if !ok {
		panic(fmt.Sprintf("session %s not found", key))
	}
	return value
}

// 设置 session 值
func SetValue[T any](ctx context.Context, key string, value T) {
	GetSession(ctx).values[key] = value
}

// session 中间件
var SessionManager = func(ctx context.Context, next func(ctx context.Context)) {
	c := ctx.Value(constant.Container).(*container.Container)
	request := container.GetMustSingletonByAlias[*http.Request](c, constant.Request)
	response := container.GetMustImplement[http.ResponseWriter](c)
	sessionName := getEnv("SESSION_NAME", "sid")
	cookie, err := request.Cookie(sessionName)
	if err == http.ErrNoCookie {
		cookie = &http.Cookie{
			Name:     sessionName,
			Value:    fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%d", time.Now().UnixNano(), rand.Intn(1000000))))),
			Path:     "/",
			HttpOnly: true,
		}
		request.AddCookie(cookie)
	}

	session, ok := sessionMap[cookie.Value]
	if !ok {
		session = &Session{
			values:     make(map[string]any),
			lastReadAt: time.Now(),
		}
		sessionMap[cookie.Value] = session
	} else {
		session.lastReadAt = time.Now()
	}
	container.SetSingletonWithAlias(c, constant.Session, session)

	next(ctx)

	cookie.Expires = time.Now().Add(getSessionExpire())
	response.Header().Add("Set-Cookie", cookie.String())
}

// 获取 session 过期时间
func getSessionExpire() time.Duration {
	sessionExpire := getEnv("SESSION_EXPIRE", "30m")
	expire, err := time.ParseDuration(sessionExpire)
	if err != nil {
		expire = time.Minute * 30
	}
	return expire
}

// 获取环境变量
func getEnv(key string, def string) string {
	if v, ok := envCache[key]; ok {
		return v
	}
	v := os.Getenv(key)
	if v == "" {
		return def
	}
	envCache[key] = v
	return v
}
